//! Utility functions for the NRC compiler

use std::collections::HashMap;
use std::path::Path;

/// Utility functions for file operations
pub mod file_utils {
    use super::*;

    /// Read a file and return its contents
    pub fn read_file(path: &Path) -> Result<String, std::io::Error> {
        std::fs::read_to_string(path)
    }

    /// Write contents to a file
    pub fn write_file(path: &Path, contents: &str) -> Result<(), std::io::Error> {
        std::fs::write(path, contents)
    }

    /// Check if a file exists
    pub fn file_exists(path: &Path) -> bool {
        path.exists()
    }
}

/// Utility functions for string operations
pub mod string_utils {
    /// Escape a string for use in LLVM IR
    pub fn escape_llvm_string(s: &str) -> String {
        s.chars()
            .map(|c| match c {
                '"' => "\\22".to_string(),
                '\\' => "\\5C".to_string(),
                '\n' => "\\0A".to_string(),
                '\r' => "\\0D".to_string(),
                '\t' => "\\09".to_string(),
                c if c.is_ascii() && c.is_control() => {
                    format!("\\{:02X}", c as u8)
                }
                c => c.to_string(),
            })
            .collect()
    }

    /// Convert a string to a valid identifier
    pub fn to_valid_identifier(s: &str) -> String {
        s.chars()
            .map(|c| {
                if c.is_alphanumeric() || c == '_' {
                    c
                } else {
                    '_'
                }
            })
            .collect()
    }
}

/// Utility functions for collections
pub mod collection_utils {
    use super::*;

    /// Create a HashMap from a vector of key-value pairs
    pub fn hashmap_from_pairs<K, V>(pairs: Vec<(K, V)>) -> HashMap<K, V>
    where
        K: std::hash::Hash + Eq,
    {
        pairs.into_iter().collect()
    }

    /// Get the first element from a vector if it exists
    pub fn first<T>(vec: &[T]) -> Option<&T> {
        vec.first()
    }

    /// Get the last element from a vector if it exists
    pub fn last<T>(vec: &[T]) -> Option<&T> {
        vec.last()
    }
}

/// Utility functions for error handling
pub mod error_utils {
    use crate::error::{CompilerError, Location};

    /// Create a compiler error with location
    pub fn create_error(location: Location, message: &str) -> CompilerError {
        CompilerError::semantic(location.line, location.column, message)
    }

    /// Create a compiler error without location
    pub fn create_error_no_location(message: &str) -> CompilerError {
        CompilerError::internal(message)
    }
}

/// Utility functions for name mangling
pub mod mangling {
    use super::string_utils;
    use crate::ast::types::*;
    use crate::ast::{FunctionSignature, Type};

    /// Produce a unique LLVM-safe name for a function signature.
    pub fn mangle_function_name(signature: &FunctionSignature) -> String {
        if signature.parameter_types.is_empty() {
            return format!("{}__void", signature.name);
        }
        let encoded: Vec<String> = signature
            .parameter_types
            .iter()
            .map(|ty| encode_type(ty))
            .collect();
        format!("{}__{}", signature.name, encoded.join("_"))
    }

    /// Generate a stable token for a type (ignoring location metadata).
    pub fn type_tag(ty: &Type) -> String {
        encode_type(ty)
    }

    fn encode_type(ty: &Type) -> String {
        match ty {
            Type::Basic(basic) => encode_basic_type(basic),
            Type::Generic(name) => format!("gen{}", string_utils::to_valid_identifier(name)),
            Type::Array(array_type) => {
                format!("arr{}", encode_type(&array_type.element_type))
            }
            Type::Map(map_type) => format!(
                "map{}_{}",
                encode_type(&map_type.key_type),
                encode_type(&map_type.value_type)
            ),
            Type::Tuple(tuple_type) => {
                let parts: Vec<String> = tuple_type
                    .element_types
                    .iter()
                    .map(|t| encode_type(t))
                    .collect();
                format!("tuple{}", parts.join("_"))
            }
            Type::Struct(struct_type) => {
                let mut tag =
                    format!("struct{}", string_utils::to_valid_identifier(&struct_type.name));
                if !struct_type.type_args.is_empty() {
                    let args: Vec<String> = struct_type
                        .type_args
                        .iter()
                        .map(|t| encode_type(t))
                        .collect();
                    tag = format!("{}__{}", tag, args.join("_"));
                }
                tag
            }
            Type::Enum(enum_type) => {
                let mut tag =
                    format!("enum{}", string_utils::to_valid_identifier(&enum_type.name));
                if !enum_type.type_args.is_empty() {
                    let args: Vec<String> = enum_type
                        .type_args
                        .iter()
                        .map(|t| encode_type(t))
                        .collect();
                    tag = format!("{}__{}", tag, args.join("_"));
                }
                tag
            }
            Type::Function(func_type) => {
                let params: Vec<String> = func_type
                    .parameter_types
                    .iter()
                    .map(|t| encode_type(t))
                    .collect();
                let return_type = func_type
                    .return_type
                    .as_ref()
                    .map(|t| encode_type(t))
                    .unwrap_or_else(|| "void".to_string());
                format!("fn{}_{}", params.join("_"), return_type)
            }
            Type::Reference(reference_type) => {
                format!("ref{}", encode_type(&reference_type.referenced_type))
            }
            Type::Vec(vec_type) => format!("vec{}", encode_type(&vec_type.element_type)),
            Type::Channel(channel_type) => {
                format!("chan{}", encode_type(&channel_type.element_type))
            }
            Type::Optional(optional_type) => {
                format!("opt{}", encode_type(&optional_type.inner_type))
            }
            Type::Error(error_type) => format!("err{}", encode_type(&error_type.inner_type)),
            Type::Result(result_type) => format!(
                "result{}_{}",
                encode_type(&result_type.ok_type),
                encode_type(&result_type.err_type)
            ),
            Type::Option(option_type) => {
                format!("opt{}", encode_type(&option_type.some_type))
            }
            Type::Alias(alias_type) => {
                let mut tag =
                    format!("alias{}", string_utils::to_valid_identifier(&alias_type.name));
                if !alias_type.type_args.is_empty() {
                    let args: Vec<String> = alias_type
                        .type_args
                        .iter()
                        .map(|t| encode_type(t))
                        .collect();
                    tag = format!("{}__{}", tag, args.join("_"));
                }
                tag
            }
            Type::Union(union_type) => {
                let parts: Vec<String> = union_type
                    .member_types
                    .iter()
                    .map(|t| encode_type(t))
                    .collect();
                format!("union{}", parts.join("_"))
            }
            Type::Rc(rc_type) => format!("rc{}", encode_type(&rc_type.inner_type)),
            Type::Weak(weak_type) => format!("weak{}", encode_type(&weak_type.inner_type)),
        }
    }

    fn encode_basic_type(basic: &BasicType) -> String {
        match basic {
            BasicType::Int => "int".to_string(),
            BasicType::Int8 => "i8".to_string(),
            BasicType::Int16 => "i16".to_string(),
            BasicType::Int32 => "i32".to_string(),
            BasicType::Int64 => "i64".to_string(),
            BasicType::Uint => "uint".to_string(),
            BasicType::Uint8 => "u8".to_string(),
            BasicType::Uint16 => "u16".to_string(),
            BasicType::Uint32 => "u32".to_string(),
            BasicType::Uint64 => "u64".to_string(),
            BasicType::Float32 => "f32".to_string(),
            BasicType::Float64 => "f64".to_string(),
            BasicType::Bool => "bool".to_string(),
            BasicType::String => "str".to_string(),
            BasicType::Char => "char".to_string(),
            BasicType::Rune => "rune".to_string(),
            BasicType::Any => "any".to_string(),
            BasicType::Void => "void".to_string(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::path::PathBuf;
    use tempfile::TempDir;

    mod file_utils_tests {
        use super::*;

        #[test]
        fn test_read_file() {
            let temp_dir = TempDir::new().unwrap();
            let file_path = temp_dir.path().join("test_file.txt");
            fs::write(&file_path, "Hello, World!").unwrap();

            let result = file_utils::read_file(&file_path);
            assert!(result.is_ok());
            assert_eq!(result.unwrap(), "Hello, World!");
        }

        #[test]
        fn test_read_file_not_found() {
            let file_path = PathBuf::from("/nonexistent/file.txt");
            let result = file_utils::read_file(&file_path);
            assert!(result.is_err());
        }

        #[test]
        fn test_write_file() {
            let temp_dir = TempDir::new().unwrap();
            let file_path = temp_dir.path().join("output.txt");
            let content = "Test content";

            let result = file_utils::write_file(&file_path, content);
            assert!(result.is_ok());

            // Verify the content was written correctly
            let read_content = fs::read_to_string(&file_path).unwrap();
            assert_eq!(read_content, content);
        }

        #[test]
        fn test_file_exists() {
            let temp_dir = TempDir::new().unwrap();
            let file_path = temp_dir.path().join("existing_file.txt");
            fs::write(&file_path, "test").unwrap();

            assert!(file_utils::file_exists(&file_path));

            let nonexistent = temp_dir.path().join("nonexistent.txt");
            assert!(!file_utils::file_exists(&nonexistent));
        }
    }

    mod string_utils_tests {
        use super::*;

        #[test]
        fn test_escape_llvm_string_basic() {
            let input = "hello";
            let output = string_utils::escape_llvm_string(input);
            assert_eq!(output, "hello");
        }

        #[test]
        fn test_escape_llvm_string_quotes() {
            let input = "say \"hello\"";
            let output = string_utils::escape_llvm_string(input);
            assert_eq!(output, "say \\22hello\\22");
        }

        #[test]
        fn test_escape_llvm_string_backslash() {
            let input = "path\\to\\file";
            let output = string_utils::escape_llvm_string(input);
            assert_eq!(output, "path\\5Cto\\5Cfile");
        }

        #[test]
        fn test_escape_llvm_string_newline() {
            let input = "line1\nline2";
            let output = string_utils::escape_llvm_string(input);
            assert_eq!(output, "line1\\0Aline2");
        }

        #[test]
        fn test_escape_llvm_string_carriage_return() {
            let input = "line1\rline2";
            let output = string_utils::escape_llvm_string(input);
            assert_eq!(output, "line1\\0Dline2");
        }

        #[test]
        fn test_escape_llvm_string_tab() {
            let input = "col1\tcol2";
            let output = string_utils::escape_llvm_string(input);
            assert_eq!(output, "col1\\09col2");
        }

        #[test]
        fn test_escape_llvm_string_control_chars() {
            let input = "text\x00null";
            let output = string_utils::escape_llvm_string(input);
            assert!(output.contains("\\00"));
        }

        #[test]
        fn test_escape_llvm_string_multiple_special() {
            let input = "say \"hi\"\nwith\ttab";
            let output = string_utils::escape_llvm_string(input);
            assert!(output.contains("\\22"));
            assert!(output.contains("\\0A"));
            assert!(output.contains("\\09"));
        }

        #[test]
        fn test_to_valid_identifier_basic() {
            let input = "hello";
            let output = string_utils::to_valid_identifier(input);
            assert_eq!(output, "hello");
        }

        #[test]
        fn test_to_valid_identifier_with_underscore() {
            let input = "hello_world";
            let output = string_utils::to_valid_identifier(input);
            assert_eq!(output, "hello_world");
        }

        #[test]
        fn test_to_valid_identifier_with_numbers() {
            let input = "var123";
            let output = string_utils::to_valid_identifier(input);
            assert_eq!(output, "var123");
        }

        #[test]
        fn test_to_valid_identifier_with_special_chars() {
            let input = "hello-world!@#";
            let output = string_utils::to_valid_identifier(input);
            assert_eq!(output, "hello_world___");
        }

        #[test]
        fn test_to_valid_identifier_unicode() {
            let input = "你好世界";
            let output = string_utils::to_valid_identifier(input);
            // Unicode characters are not alphanumeric or underscore, but the function
            // only replaces non-alphanumeric/underscore with underscore for ASCII chars.
            // Unicode chars remain as-is in the current implementation.
            assert_eq!(output, input);
        }

        #[test]
        fn test_to_valid_identifier_mixed() {
            let input = "my_var-name@123";
            let output = string_utils::to_valid_identifier(input);
            // "-" and "@" are replaced with "_"
            assert_eq!(output, "my_var_name_123");
        }
    }

    mod collection_utils_tests {
        use super::*;

        #[test]
        fn test_hashmap_from_pairs() {
            let pairs = vec![
                ("a".to_string(), 1),
                ("b".to_string(), 2),
                ("c".to_string(), 3),
            ];
            let map = collection_utils::hashmap_from_pairs(pairs);

            assert_eq!(map.len(), 3);
            assert_eq!(map.get("a"), Some(&1));
            assert_eq!(map.get("b"), Some(&2));
            assert_eq!(map.get("c"), Some(&3));
        }

        #[test]
        fn test_hashmap_from_pairs_empty() {
            let pairs: Vec<(String, i32)> = vec![];
            let map = collection_utils::hashmap_from_pairs(pairs);
            assert_eq!(map.len(), 0);
        }

        #[test]
        fn test_hashmap_from_pairs_duplicate_keys() {
            let pairs = vec![
                ("a".to_string(), 1),
                ("a".to_string(), 2), // duplicate key
            ];
            let map = collection_utils::hashmap_from_pairs(pairs);

            // Last value should overwrite
            assert_eq!(map.len(), 1);
            assert_eq!(map.get("a"), Some(&2));
        }

        #[test]
        fn test_first() {
            let vec = vec![1, 2, 3, 4, 5];
            assert_eq!(collection_utils::first(&vec), Some(&1));
        }

        #[test]
        fn test_first_empty() {
            let vec: Vec<i32> = vec![];
            assert_eq!(collection_utils::first(&vec), None);
        }

        #[test]
        fn test_last() {
            let vec = vec![1, 2, 3, 4, 5];
            assert_eq!(collection_utils::last(&vec), Some(&5));
        }

        #[test]
        fn test_last_empty() {
            let vec: Vec<i32> = vec![];
            assert_eq!(collection_utils::last(&vec), None);
        }

        #[test]
        fn test_last_single_element() {
            let vec = vec![42];
            assert_eq!(collection_utils::last(&vec), Some(&42));
        }
    }

    mod error_utils_tests {
        use super::*;
        use crate::error::{CompilerError, Location};

        #[test]
        fn test_create_error() {
            let location = Location::new(10, 5, 100);
            let error = error_utils::create_error(location, "Test error message");

            match error {
                CompilerError::Semantic {
                    line,
                    column,
                    message,
                    ..
                } => {
                    assert_eq!(line, 10);
                    assert_eq!(column, 5);
                    assert_eq!(message, "Test error message");
                }
                _ => panic!("Expected Semantic error"),
            }
        }

        #[test]
        fn test_create_error_no_location() {
            let error = error_utils::create_error_no_location("Internal error");

            match error {
                CompilerError::Internal { message, .. } => {
                    assert_eq!(message, "Internal error");
                }
                _ => panic!("Expected Internal error"),
            }
        }
    }
}
